#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>

extern unsigned long _text, _etext, _end;

unsigned long high_memory = 0;

int nr_free_pages = 0;
unsigned long free_page_list = 0;

unsigned short *mem_map = NULL; // 管理物理地址

static void free_one_table(unsigned long *page_dir) {
    int j;
    unsigned long pg_table = *page_dir;
    unsigned long *page_table;

    if (!pg_table)
        return;
    *page_dir = 0;
    if (__va(pg_table) >= high_memory || !(pg_table & PAGE_PRESENT)) {
        printk("Bad page table: [%p]=%08lx\n", page_dir, pg_table);
        return;
    }
    if (mem_map[pg_table >> PAGE_SHIFT] & MAP_PAGE_RESERVED)
        return;
    page_table = (unsigned long *)__va(pg_table & PAGE_MASK);
    for (j = 0; j < PTRS_PER_PAGE; j++, page_table++) {
        unsigned long pg = *page_table;
        if (!pg || mem_map[pg >> PAGE_SHIFT] & MAP_PAGE_RESERVED)
            continue;
        *page_table = 0;
        if (pg & PAGE_PRESENT)
            free_page(__va(PAGE_MASK & pg));
    }
    free_page(__va(PAGE_MASK & pg_table));
}

void free_page_tables(struct task_struct *tsk) {
    int i;
    unsigned long pg_dir;
    unsigned long *page_dir;

    if (!tsk)
        return;
    if (tsk == task[0]) {
        printk("task[0] (swapper) killed: unable to recover\n");
        panic("Trying to free up swapper memory space");
    }
    pg_dir = tsk->tss.cr3;
    if (!pg_dir || pg_dir == (unsigned long)swapper_pg_dir) {
        printk("Trying to free kernel page-directory: not good\n");
        return;
    }
    tsk->tss.cr3 = (unsigned long)swapper_pg_dir;
    if (tsk == current)
        __asm__ __volatile__("movl %0, %%cr3"
                             :
                             : "a"(tsk->tss.cr3));
    if (mem_map[MAP_NR(pg_dir)] > 1) {
        free_page(pg_dir);
        return;
    }
    page_dir = (unsigned long *)pg_dir;
    for (i = 0; i < PTRS_PER_PAGE; i++, page_dir++)
        free_one_table(page_dir);
    free_page(pg_dir);
    invalidate();
}

int copy_page_tables(struct task_struct *tsk) {
    int i;
    unsigned long old_pg_dir, *old_page_dir;
    unsigned long new_pg_dir, *new_page_dir;

    new_pg_dir = get_free_page(GFP_KERNEL);
    if (!new_pg_dir)
        return -ENOMEM;
    old_pg_dir = current->tss.cr3;
    tsk->tss.cr3 = __pa(new_pg_dir);    // cr3要填写物理地址
    old_page_dir = (unsigned long *)__va(old_pg_dir);
    new_page_dir = (unsigned long *)new_pg_dir;
    for (i = 0; i < PTRS_PER_PAGE; i++, old_page_dir++, new_page_dir++) {
        int j;
        unsigned long old_pg_table, *old_page_table;
        unsigned long new_pg_table, *new_page_table;

        old_pg_table = *old_page_dir;
        if (!old_pg_table)
            continue;
        if (__va(old_pg_table) >= high_memory || !(old_pg_table & PAGE_PRESENT)) {
            printk("copy_page_tables: bad page table: "
                   "probable memory corruption");
            *old_page_dir = 0;
            continue;
        }
        if (mem_map[old_pg_table >> PAGE_SHIFT] & MAP_PAGE_RESERVED) {
            *new_page_dir = old_pg_table;
            continue;
        }
        new_pg_table = get_free_page(GFP_KERNEL);
        if (!new_pg_table) {
            free_page_tables(tsk);
            return -ENOMEM;
        }
        old_page_table = (unsigned long *)__va(PAGE_MASK & old_pg_table);
        new_page_table = (unsigned long *)new_pg_table;
        for (j = 0; j < PTRS_PER_PAGE; j++, old_page_table++, new_page_table++) {
            unsigned long pg;
            pg = *old_page_table;
            if (!(pg & PAGE_PRESENT))
                continue;
            if ((pg & PAGE_RW) == PAGE_RW)
                pg &= ~PAGE_RW;
            *new_page_table = pg;
            if (mem_map[pg >> PAGE_SHIFT] & MAP_PAGE_RESERVED)
                continue;
            *old_page_table = pg;
            mem_map[pg >> PAGE_SHIFT]++;
        }
        *new_page_dir = __pa(new_pg_table) | PAGE_TABLE;
    }
    invalidate();
    return 0;
}

unsigned long paging_init(unsigned long start_mem, unsigned long end_mem) {
    unsigned long *pg_dir;
    unsigned long *pg_table;
    unsigned long tmp;
    unsigned long address;

    start_mem = PAGE_ALIGN(start_mem);
    address = PAGE_OFFSET;
    pg_dir = swapper_pg_dir;
    pg_dir[0] = 0;               // 取消低地址的映射
    pg_dir += PAGE_OFFSET >> 22; // 定位到内核的页表
    // 遍历页目录项
    while (address < end_mem) {
        tmp = *pg_dir;
        if (!tmp) {
            tmp = __pa(start_mem) | PAGE_TABLE;
            start_mem += PAGE_SIZE;
        }
        *pg_dir = tmp;
        pg_dir++;
        pg_table = (unsigned long *)__va(tmp & PAGE_MASK);
        // 遍历页表项
        for (tmp = 0; tmp < PTRS_PER_PAGE; tmp++, pg_table++) {
            if (address < end_mem)
                *pg_table = __pa(address) | PAGE_SHARED;
            else
                *pg_table = 0;
            address += PAGE_SIZE;
        }
    }
    invalidate(); // 刷新页目录
    return start_mem;
}

void mem_init(unsigned long start_mem, unsigned long end_mem) {
    unsigned long start_low_mem;
    int codepages = 0;
    int reservedpages = 0;
    int datapages = 0;
    unsigned long tmp;
    unsigned short *p;

    end_mem &= PAGE_MASK;
    high_memory = end_mem;
    start_mem += 0x0000000f;
    start_mem &= ~0x0000000f;
    tmp = MAP_NR(end_mem);

    mem_map = (unsigned short *)start_mem;
    p = mem_map + tmp;
    start_mem = (unsigned long)p;
    memset(mem_map, 0, start_mem - (unsigned long)mem_map);
    while (p > mem_map)
        *(--p) = MAP_PAGE_RESERVED;
    start_low_mem = PAGE_OFFSET;
    start_mem = PAGE_ALIGN(start_mem);
    // 将0到0xA0000的物理页面标记为可以使用
    while (start_low_mem < __va(0xA0000)) {
        mem_map[MAP_NR(start_low_mem)] = 0;
        start_low_mem += PAGE_SIZE;
    }
    // 将start_mem到end_mem的物理页面标记为可以使用
    while (start_mem < end_mem) {
        mem_map[MAP_NR(start_mem)] = 0;
        start_mem += PAGE_SIZE;
    }

    free_page_list = 0;
    nr_free_pages = 0;
    for (tmp = PAGE_OFFSET; tmp < end_mem; tmp += PAGE_SIZE) {
        if (mem_map[MAP_NR(tmp)]) {
            if (tmp >= (unsigned long)&_text && tmp < (unsigned long)&_end) {
                if (tmp < (unsigned long)&_etext)
                    codepages++;
                else
                    datapages++;
            } else
                reservedpages++;
            continue;
        }
        *(unsigned long *)tmp = free_page_list;
        free_page_list = tmp;
        nr_free_pages++;
    }
    tmp = nr_free_pages << PAGE_SHIFT;
    printk("Memory: %luk/%luk available (%dk kernel code, %dk reserved, %dk data)\n",
           tmp >> 10,
           __pa(end_mem) >> 10,
           codepages << (PAGE_SHIFT - 10),
           reservedpages << (PAGE_SHIFT - 10),
           datapages << (PAGE_SHIFT - 10));
}